package me.sealer.ssm.token;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.sealer.ssm.constant.TokenConstant;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 功能描述： $ DefaultTokenManager
 *
 * @author sealer
 * @email 1178884049@qq.com
 * @date 2019年12月02日 13时01分25秒
 */
@Slf4j
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DefaultTokenManager implements TokenManager {
    /**
     * 最多保留验证码个数， 默认3
     */
    private int maxEntryNumber = 3;

    /**
     * 验证码有效时间（单位：秒）， 默认60s
     */
    private int delayTime = 60;

    /**
     * session中存储的存放token的list的key, 默认 ”_SSM_TOKEN_LIST“
     */
    private String tokenListName = "_SSM_TOKEN_LIST";

    /**
     * 前端上送验证码时， 存放验证码的字段名称， 默认 "_tokenName"
     */
    private String tokenName = "_tokenName";

    /**
     * 验证码的字符个数，即验证码位数， 默认6
     */
    private int tokenLength = 6;

    /**
     * 生成时， 是否将相似字符进行模糊替换；
     * 验证时，是否忽略大小写。
     */
    private boolean ignoreCase = false;

    /**
     * token串字符来源库， 默认为10进制数字+大小写拉丁字母的组合
     */
    private String chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

    /**
     * 设置一些field校验， 看上传参数与约定参数是否相等，不一致抛异常。
     */
    private String[] verifiedFields;

    @Override
    public Token createToken(HttpServletRequest request) {
        HttpSession session = request.getSession();
        TokenList tokenList = (TokenList) session.getAttribute(this.tokenListName);
        if (tokenList == null) {
            tokenList = new TokenList(this.maxEntryNumber, this.delayTime, this.tokenLength, this.chars.toCharArray());
        }

        String tokenId = tokenList.getNextTokenId();

        Token token = new TokenImpl(tokenId, System.currentTimeMillis());
        if (this.verifiedFields != null) {
            Map props = new HashMap(8);

            for (int i = 0; i < this.verifiedFields.length; ++i) {
                String field = this.verifiedFields[i];
                Object value = request.getParameter(field);
                if (value != null) {
                    props.put(field, value);
                }
            }
            token.setProperties(props);
        }

        tokenList.add(token);

        session.setAttribute(this.tokenListName, tokenList);

        if (log.isDebugEnabled()) {
            log.debug("Create Token" + this.tokenListName + "," + this.tokenName + " " + token);
        }

        return token;
    }

    @Override
    public int verifyToken(HttpServletRequest request) {
        HttpSession session = request.getSession();
        TokenList tokenList = (TokenList) session.getAttribute(this.tokenListName);
        if (log.isDebugEnabled()) {
            log.debug("Verify Token " + this.tokenListName + "," + this.tokenName + " " + tokenList);
        }

        if (tokenList == null) {
            return TokenConstant.TOKEN_LIST_IS_NULL;
        }

        String tokenId = request.getParameter(this.tokenName);
        if (log.isDebugEnabled()) {
            log.debug("Get Token " + this.tokenListName + "," + this.tokenName + " " + tokenId);
        }

        // 上送的tokenId is null
        if (tokenId == null) {
            return TokenConstant.TOKEN_ID_IS_NULL;
        }

        Token token;
        try {
            token = tokenList.get(tokenId, this.ignoreCase);
        } catch (Exception e) {
            // 此异常是TokenList中的get方法获取到的token超时抛出的。
            return TokenConstant.TOKEN_TIMEOUT;
        }

        if (token == null) {
            return TokenConstant.TOKEN_IS_NULL;
        }

        Map props = token.getProperties();
        if (props == null) {
            props = new HashMap(4);
        }

        if (this.verifiedFields != null) {
            for (int i = 0; i < this.verifiedFields.length; ++i) {
                String field = this.verifiedFields[i];
                Object obj1 = request.getParameter(field);
                Object obj2 = props.get(field);
                if (!Objects.equals(obj1, obj2)) {
                    if (log.isDebugEnabled()) {
                        log.debug("Verify Token " + this.tokenListName + "," + this.tokenName + " " + tokenId + ", field " + field + " not equal:" + obj1 + "," + obj2);
                    }
                    return TokenConstant.TOKEN_VERIFIED_FIELDS_NOT_EQUALS;
                }
            }
        }

        session.setAttribute(this.tokenListName, tokenList);

        return TokenConstant.TOKEN_SUCCESS;
    }

    public void setMaxEntryNumber(int maxEntryNumber) {
        this.maxEntryNumber = maxEntryNumber;
    }

    public void setDelayTime(int delayTime) {
        this.delayTime = delayTime;
    }

    public void setTokenListName(String tokenListName) {
        this.tokenListName = tokenListName;
    }

    public void setTokenName(String tokenName) {
        this.tokenName = tokenName;
    }

    public void setTokenLength(int tokenLength) {
        this.tokenLength = tokenLength;
    }

    public void setIgnoreCase(boolean ignoreCase) {
        this.ignoreCase = ignoreCase;
    }

    public void setChars(String chars) {
        this.chars = chars;
    }

    public void setVerifiedFields(String[] verifiedFields) {
        this.verifiedFields = verifiedFields;
    }
}
